/** @file
  The module entry point for Tcg2 configuration module.

Copyright (c) 2017 - 2018, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/


#include <PiPei.h>

#include <IndustryStandard/UefiTcgPlatform.h>
#include <Guid/TcgEventHob.h>
#include <Guid/TpmInstance.h>
#include <Guid/PttInstance.h>
#include <Guid/PttPTPInstanceGuid.h>
#include <PttPtpRegs.h>
#include <Library/HashLib.h>
#include <Library/HobLib.h>
#include <Library/BootGuardLib.h>
#include <Library/Tpm12CommandLib.h>
#include <Library/Tpm2CommandLib.h>
#include <Library/Tpm2DeviceLib.h>
#include <MeChipset.h>
#include <Library/MmPciLib.h>
#include <Library/IoLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/ReportStatusCodeLib.h>
#include <Library/PeiServicesLib.h>
#include <Library/PcdLib.h>

#include <Ppi/ReadOnlyVariable2.h>
#include <Ppi/TpmInitialized.h>
#include <Protocol/Tcg2Protocol.h>

#include "Tcg2ConfigNvData.h"
#include <Library/PeiBootGuardEventLogLib.h>


TPM_INSTANCE_ID  mTpmInstanceId[] = TPM_INSTANCE_ID_LIST;

CONST EFI_PEI_PPI_DESCRIPTOR gTpmSelectedPpi = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiTpmDeviceSelectedGuid,
  NULL
};

EFI_PEI_PPI_DESCRIPTOR  mTpmInitializationDonePpiList = {
  EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST,
  &gPeiTpmInitializationDonePpiGuid,
  NULL
};

//
// TPM Base Address Definitions
//
#define PTT_BASE_ADDRESS  0xFED40000
#define PTT_STATUS_REG    0x44
/**
  This routine check both SetupVariable and real TPM device, and return final TpmDevice configuration.

  @param  SetupTpmDevice  TpmDevice configuration in setup driver

  @return TpmDevice configuration
**/
UINT8
DetectTpmDevice (
  IN UINT8 SetupTpmDevice
  );


/**
  Check CPU Family

  @retval TRUE              ULT CPU
          FALSE             Trad CPU
**/
BOOLEAN
EFIAPI
IsUltCpu (
  VOID
  )
{
  UINT32             CpuidEax;

  CpuidEax = 0;

  ///
  /// Read the CPUID information
  ///
  AsmCpuid (1, &CpuidEax, NULL, NULL, NULL);

  if ((CpuidEax & 0x0FFF0FF0) == 0x40650) {
    //
    // ULT CPU
    //
    return TRUE;
  } else {
    //
    // Trad CPU
    //
    return FALSE;
  }
}

/**
  Update Tpm Policy for Boot Guard

  @param[in] BootGuardInfo - Pointer to BootGuardInfo.

  @retval EFI_SUCCES             Convert variable to PCD successfully.
  @retval EFI_UNSUPPORTED        Boot Guard or Boot Guard measure boot is not support.
**/
EFI_STATUS
UpdateBootGuardTpmPolicy (
  IN BOOT_GUARD_INFO *BootGuardInfo
)
{
  UINTN           Size;

  Size = sizeof (EFI_GUID);

  DEBUG ((DEBUG_INFO, "Boot Guard Policy Initialization\n"));

  //
  // Check if platform supports Boot Guard
  //
  if (BootGuardInfo->BootGuardCapability == TRUE) {
    DEBUG ((DEBUG_INFO, "Boot Guard Support is enabled on Platform\n"));
    //
    // If Boot Guard is supported, check if ME FW indcates to Disconnect All TPM's
    //
    if (BootGuardInfo->DisconnectAllTpms == TRUE) {
      //
      // RPPO-KBL-0057: RoyalParkOverrideBegin
      //
      PcdSetPtrS (PcdTpmInstanceGuid, &Size, &gEfiTpmDeviceInstanceNoneGuid);
      DEBUG ((DEBUG_INFO, "Boot Guard - All TPM's are Disconnected\n"));
    } else {
      if (BootGuardInfo->MeasuredBoot == FALSE) {
        //
        // No TPM initiated by ACM. BIOS to continue measured boot
        // based on BIOS Policies.
        //
        DEBUG ((DEBUG_INFO, "Boot Guard - MeasuredBoot == FALSE\n"));
        return EFI_UNSUPPORTED;
      }

      if (BootGuardInfo->BypassTpmInit == TRUE) {
        //
        // 1. Identify TPM Type Selected by ACM
        // 2. Skip TPM Startup & SCRTM
        // 3. Bypass TPM Extends in BIOS for PEI FV
        //
        DEBUG ((DEBUG_INFO, "TPM is Successfully initialized by Boot Guard ACM\n"));
        switch(BootGuardInfo->TpmType) {
          case dTpm12:
            PcdSet8S (PcdTpmInitializationPolicy, 0);
            PcdSetPtrS (PcdTpmInstanceGuid, &Size, &gEfiTpmDeviceInstanceTpm12Guid);
            DEBUG ((DEBUG_INFO, "Boot Guard - dTPM 1.2\n"));
            break;

          case dTpm20:
            PcdSet8S (PcdTpm2InitializationPolicy, 0);
            PcdSetPtrS (PcdTpmInstanceGuid, &Size, &gEfiTpmDeviceInstanceTpm20DtpmGuid);
            DEBUG ((DEBUG_INFO, "Boot Guard - dTPM 2.0\n"));
            break;

          case Ptt:
            PcdSet8S (PcdTpm2InitializationPolicy, 0);
            PcdSet8S (PcdTpm2SelfTestPolicy, 0);
            PcdSetPtrS (PcdTpmInstanceGuid, &Size, &gTpmDeviceInstanceTpm20PttPtpGuid);
            DEBUG ((DEBUG_INFO, "Boot Guard - PTT\n"));
            break;

          default:
            //
            // Unknown Type
            //
            PcdSetPtrS (PcdTpmInstanceGuid, &Size, &gEfiTpmDeviceInstanceNoneGuid);
            ASSERT (FALSE);
            break;
          }
      } else {
          //
          // If ACM fails to initialize TPM successfully BIOS should
          // Try to Enumerate TPM
          //
          PcdSetPtrS (PcdTpmInstanceGuid, &Size, &gEfiTpmDeviceInstanceNoneGuid);
          return EFI_UNSUPPORTED;
        } // End TPM Success
    } // End Disconnect All TPM's flow
  } else {
    return EFI_UNSUPPORTED;
  } // End BootGuardCapability

  return EFI_SUCCESS;
}

/**
  Do a hash operation on a data buffer, extend a specific TPM PCR with the hash result,
  and build a GUIDed HOB recording the event which will be passed to the DXE phase and
  added into the Event Log.

  @param[in]      Flags         Bitmap providing additional information.
  @param[in]      HashData      Physical address of the start of the data buffer
                                to be hashed, extended, and logged.
  @param[in]      HashDataLen   The length, in bytes, of the buffer referenced by HashData.
  @param[in]      NewEventHdr   Pointer to a TCG_PCR_EVENT_HDR data structure.
  @param[in]      NewEventData  Pointer to the new event data.

  @retval EFI_SUCCESS           Operation completed successfully.
  @retval EFI_OUT_OF_RESOURCES  No enough memory to log the new event.
  @retval EFI_DEVICE_ERROR      The command was unsuccessful.

**/
EFI_STATUS
HashLogExtendEvent (
  IN      UINT64                    Flags,
  IN      UINT8                     *HashData,
  IN      UINTN                     HashDataLen,
  IN      TCG_PCR_EVENT_HDR         *NewEventHdr,
  IN      UINT8                     *NewEventData
  )
{
  EFI_STATUS                        Status;
  TPML_DIGEST_VALUES                DigestList;

  if (GetFirstGuidHob (&gTpmErrorHobGuid) != NULL) {
    return EFI_DEVICE_ERROR;
  }

  Status = HashAndExtend (
             NewEventHdr->PCRIndex,
             HashData,
             HashDataLen,
             &DigestList
             );
  if (!EFI_ERROR (Status)) {
    if ((Flags & EFI_TCG2_EXTEND_ONLY) == 0) {
      Status = LogHashEvent (&DigestList, NewEventHdr, NewEventData);
    }
  }

  if (Status == EFI_DEVICE_ERROR) {
    DEBUG ((EFI_D_ERROR, "HashLogExtendEvent - %r. Disable TPM.\n", Status));
    BuildGuidHob (&gTpmErrorHobGuid,0);
    REPORT_STATUS_CODE (
      EFI_ERROR_CODE | EFI_ERROR_MINOR,
      (PcdGet32 (PcdStatusCodeSubClassTpmDevice) | EFI_P_EC_INTERFACE_ERROR)
      );
  }

  return Status;
}

/**
   Measure and log Separator event with error, and extend the measurement result into a specific PCR.

   @param[in] PCRIndex         PCR index.

   @retval EFI_SUCCESS         Operation completed successfully.
   @retval EFI_DEVICE_ERROR    The operation was unsuccessful.

**/
EFI_STATUS
MeasureSeparatorEventWithError (
  IN      TPM_PCRINDEX              PCRIndex
  )
{
  TCG_PCR_EVENT_HDR                 TcgEvent;
  UINT32                            EventData;

  //
  // Use EventData 0x1 to indicate there is error.
  //
  EventData = 0x1;
  TcgEvent.PCRIndex  = PCRIndex;
  TcgEvent.EventType = EV_SEPARATOR;
  TcgEvent.EventSize = (UINT32)sizeof (EventData);
  return HashLogExtendEvent(0,(UINT8 *)&EventData, TcgEvent.EventSize, &TcgEvent,(UINT8 *)&EventData);
}

/**  The entry point for Tcg2 configuration driver.

  @param  FileHandle  Handle of the file being invoked.
  @param  PeiServices Describes the list of possible PEI Services.

  @retval EFI_SUCCES             Convert variable to PCD successfully.
  @retval Others                 Fail to convert variable to PCD.
**/
EFI_STATUS
EFIAPI
Tcg2ConfigPeimEntryPoint (
  IN       EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{
  UINTN                           Size;
  EFI_STATUS                      Status;
  EFI_STATUS                      Status2;
  EFI_PEI_READ_ONLY_VARIABLE2_PPI *VariablePpi;
  TCG2_CONFIGURATION              Tcg2Configuration;
  UINTN                           VariableSize;
  EFI_BOOT_MODE                   BootMode;
  BOOT_GUARD_INFO                 BootGuardInfo;
  UINT32                          PttFtifReg;
  UINT32                          MeFwSts4;
  TPM_PCRINDEX                    PcrIndex;
  BOOLEAN                         S3ErrorReport = FALSE;

  PttFtifReg = MmioRead32 (R_PTT_TXT_STS_FTIF);
  DEBUG ((EFI_D_INFO, "PttFtifReg: %x\n", PttFtifReg));

  Status = PeiServicesLocatePpi (
             &gEfiPeiReadOnlyVariable2PpiGuid,
             0,
             NULL,
             (VOID **) &VariablePpi);

  ASSERT_EFI_ERROR (Status);

  VariableSize = sizeof(Tcg2Configuration);
  Status = VariablePpi->GetVariable (
                          VariablePpi,
                          TCG2_STORAGE_NAME,
                          &gTcg2ConfigFormSetGuid,
                          NULL,
                          &VariableSize,
                          &Tcg2Configuration
                          );

  DEBUG ((EFI_D_INFO, "Tcg2Configuration.TpmDevice from Setup: %x\n", Tcg2Configuration.TpmDevice));

  //
  // Set TPM Device
  // Traditional CPUs default to dTPM because they do not support PTT
  //
  if ((*(UINT8 *)(UINTN)(PTT_BASE_ADDRESS + 0x30) == 0xFF)) {
    if ((PttFtifReg & V_FTIF_FTPM_PRESENT) != V_FTIF_FTPM_PRESENT) {
      Tcg2Configuration.TpmDevice = TPM_DEVICE_1_2;
    }
  } else {
    if ((PttFtifReg & V_FTIF_FTPM_PRESENT) == V_FTIF_FTPM_PRESENT) {
      Tcg2Configuration.TpmDevice = TPM_DEVICE_PTT;
    }
  }
  DEBUG ((EFI_D_INFO, "Tcg2Configuration.TpmDevice after reading VTIF: %x\n", Tcg2Configuration.TpmDevice));

  //
  // Validation
  //
  if ((Tcg2Configuration.TpmDevice > TPM_DEVICE_MAX) ||
      (Tcg2Configuration.TpmDevice < TPM_DEVICE_MIN)) {
    Tcg2Configuration.TpmDevice = TPM_DEVICE_DEFAULT;
  }

  Status = EFI_UNSUPPORTED;
  Size = sizeof (EFI_GUID);

  ZeroMem (&BootGuardInfo, sizeof (BOOT_GUARD_INFO));
  GetBootGuardInfo (&BootGuardInfo);
  CreateBootguardEventLogEntries ();
  Status = UpdateBootGuardTpmPolicy (&BootGuardInfo);

  if (EFI_ERROR (Status)) { //Boot guard failed to set TPM policy - so BIOS should enumerate
    switch (Tcg2Configuration.TpmDevice) {
      case TPM_DEVICE_NULL:
        //TPM detection will fall through to TPM 1.2 case statement
        PcdSetPtrS (PcdTpmInstanceGuid, &Size, &gEfiTpmDeviceInstanceNoneGuid);
        DEBUG ((DEBUG_INFO, "Platform Default - User Select TPM None. BIOS enumerating...\n"));

      case TPM_DEVICE_1_2:
        //TPM detection will fall through to TPM 2.0 case statement

      case TPM_DEVICE_2_0_DTPM:
        if ((*(UINT8 *)(UINTN)PcdGet64 (PcdTpmBaseAddress) != 0xff)) {
          Status = PeiServicesGetBootMode (&BootMode);
          ASSERT_EFI_ERROR (Status);
          Status = Tpm2RequestUseTpm ();
          if (EFI_ERROR (Status)) {
            DEBUG ((DEBUG_ERROR, "dTPM not detected!\n"));
            break;
          }

          DEBUG ((DEBUG_INFO, "dTPM 1.2 Startup\n"));
          //
          // Check Bit 10 in ME FWSTS4 to Sx Type
          //
          MeFwSts4 = MmioRead32 (MmPciBase (ME_BUS, ME_DEVICE_NUMBER, HECI_FUNCTION_NUMBER) + R_ME_HFS_4);
          DEBUG ((DEBUG_INFO, "ME FW STS 4 = %x\n", MeFwSts4));

          if ((BootMode == BOOT_ON_S3_RESUME) || (MeFwSts4 & BIT10)) {
            Status = Tpm12Startup (TPM_ST_STATE);
          } else {
            Status = Tpm12Startup (TPM_ST_CLEAR);
          }

          if (EFI_ERROR (Status)) {
            DEBUG ((DEBUG_ERROR, "dTPM 1.2 Startup failed\n"));
          } else {
            PcdSetPtrS (PcdTpmInstanceGuid, &Size, &gEfiTpmDeviceInstanceTpm12Guid);
            DEBUG ((DEBUG_INFO, "Platform Default - dTPM 1.2\n"));
            break;
          }

          //
          // Detecting dTPM2.0
          //
          DEBUG ((DEBUG_INFO, "dTPM 2.0 Startup\n"));
          if ((BootMode == BOOT_ON_S3_RESUME) || (MeFwSts4 & BIT10)) {
            Status = Tpm2Startup (TPM_SU_STATE);

            if (EFI_ERROR (Status)) {
              Status = Tpm2Startup (TPM_SU_CLEAR);
              S3ErrorReport = TRUE;
            }
          } else {
            Status = Tpm2Startup (TPM_SU_CLEAR);
          }

          if (EFI_ERROR (Status)) {
            DEBUG ((DEBUG_ERROR, "dTPM 2.0 Startup failed\n"));
          } else {
            PcdSetPtrS (PcdTpmInstanceGuid, &Size, &gEfiTpmDeviceInstanceTpm20DtpmGuid);
            DEBUG ((DEBUG_INFO, "Platform Default - dTPM 2.0\n"));
            break;
          }
        } else {
          PcdSetPtrS (PcdTpmInstanceGuid, &Size, &gEfiTpmDeviceInstanceNoneGuid);
          DEBUG ((DEBUG_INFO, "Platform Default to TPM None. dTPM was selected based on VTIF, but no actual device connected\n"));
        }
      break;

      case TPM_DEVICE_PTT:
        if ((*(UINT8 *)(UINTN)(PTT_BASE_ADDRESS + 0x30) != 0xFF) &&
            ((PttFtifReg & V_FTIF_FTPM_PRESENT) == V_FTIF_FTPM_PRESENT)) {
          PcdSetPtrS (PcdTpmInstanceGuid, &Size, &gTpmDeviceInstanceTpm20PttPtpGuid);
          DEBUG ((DEBUG_INFO, "Platform Default - PTT\n"));
        }
        break;
    }
  }

  //
  // The system firmware that resumes from S3 MUST deal with a
  // TPM2_Startup error appropriately.
  // For example, issue a TPM2_Startup(TPM_SU_CLEAR) command and
  // configuring the device securely by taking actions like extending a
  // separator with an error digest (0x01) into PCRs 0 through 7.
  //
  if (S3ErrorReport) {
    for (PcrIndex = 0; PcrIndex < 8; PcrIndex++) {
      Status = MeasureSeparatorEventWithError (PcrIndex);
      if (EFI_ERROR (Status)) {
        DEBUG ((EFI_D_ERROR, "Separator Event with Error not Measured. Error!\n"));
      }
    }
  }


  //
  // Selection done
  //
  Status = PeiServicesInstallPpi (&gTpmSelectedPpi);
  DEBUG ((DEBUG_INFO, "PeiServicesInstallPpi gTpmSelectedPpi Status = %r\n", Status));
  ASSERT_EFI_ERROR (Status);

  //
  // Even if no TPM is selected or detected, we still need intall TpmInitializationDonePpi.
  // Because TcgPei or Tcg2Pei will not run, but we still need a way to notify other driver.
  // Other driver can know TPM initialization state by TpmInitializedPpi.
  //
  if (CompareGuid (PcdGetPtr(PcdTpmInstanceGuid), &gEfiTpmDeviceInstanceNoneGuid)) {
    Status2 = PeiServicesInstallPpi (&mTpmInitializationDonePpiList);
    DEBUG ((DEBUG_INFO, "PeiServicesInstallPpi mTpmInitializationDonePpiList Status = %r\n", Status2));
    ASSERT_EFI_ERROR (Status2);
  }

  return Status;
}
